package com.zackeus.sipservice;

import com.elvishew.xlog.XLog;
import com.zackeus.common.utils.media.MediaManager;
import com.zackeus.sipservice.constant.CaptureDevice;
import com.zackeus.sipservice.constant.RenderDevice;

import org.pjsip.pjsua2.Account;
import org.pjsip.pjsua2.AccountConfig;
import org.pjsip.pjsua2.CallInfo;
import org.pjsip.pjsua2.CallOpParam;
import org.pjsip.pjsua2.OnIncomingCallParam;
import org.pjsip.pjsua2.OnRegStartedParam;
import org.pjsip.pjsua2.OnRegStateParam;
import org.pjsip.pjsua2.pj_constants_;
import org.pjsip.pjsua2.pj_qos_type;
import org.pjsip.pjsua2.pjsip_status_code;

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @name: SipAccount
 * @author: zz
 * @date: 2022/9/20
 */
public class SipAccount extends Account {

    private static final String LOG_TAG = SipAccount.class.getSimpleName();

    private final ConcurrentHashMap<Integer, SipCall> activeCalls = new ConcurrentHashMap<>();
    private final SipAccountData data;
    private final SipService service;

    protected SipAccount(SipService service, SipAccountData data) {
        super();
        this.service = service;
        this.data = data;
    }

    /**
     * 获取正在通话ID
     * @return
     */
    public Set<Integer> getActiveCallIds() {
        return activeCalls.keySet();
    }

    public SipAccountData getData() {
        return data;
    }

    public SipCall getCall(int callId) {
        return activeCalls.get(callId);
    }

    public SipService getService() {
        return service;
    }

    AccountConfig getAccountConfig() {
        AccountConfig accountConfig = new AccountConfig();

        // account configs
        accountConfig.setIdUri(data.getIdUri());

        /* 注册配置 */
        if (data.getCallId() != null && !data.getCallId().isEmpty()) {
            accountConfig.getRegConfig().setCallID(data.getCallId());
        }
        accountConfig.getRegConfig().setRegistrarUri(data.getRegistrarUri());
        accountConfig.getRegConfig().setTimeoutSec(data.getRegExpirationTimeout());
        /* 禁用注册失败自动重试 */
        accountConfig.getRegConfig().setRetryIntervalSec(pj_constants_.PJ_FALSE);
        /* 帐户是否应在UA添加后立即注册 */
        accountConfig.getRegConfig().setRegisterOnAdd(Boolean.TRUE);

        /* sip 配置 */
        /* 凭证数组 */
        accountConfig.getSipConfig().getAuthCreds().add(data.getAuthCredInfo());
        accountConfig.getSipConfig().getProxies().add(data.getProxyUri());
        accountConfig.getSipConfig().setContactUriParams(data.getContactUriParam());
        /* 设置传输器 */
        int tpId = service.getTransportId(data.getTransportType());
        if (tpId < 0){
            throw new IllegalArgumentException("unsupported transport type: " + data.getTransportType());
        }
        accountConfig.getSipConfig().setTransportId(tpId);

        /* NAT 配置 */
        /* 控制SDP中的IP地址是否应替换为REGISTER响应的Via标头中的IP地址，仅在不使用STUN和ICE时替换 */
        /* 如果值为FALSE，则将使用本地IP地址。如果为TRUE，当STUN和ICE被禁用时，将使用在注册响应中找到的IP地址 */
        accountConfig.getNatConfig().setSdpNatRewriteUse(pj_constants_.PJ_FALSE);
        /* 只要请求使用与之前的REGISTER请求相同的传输实例，该选项用于覆盖具有与REGISTER请求中的接口地址相同的接口地址的传出消息的Via头部 */
        accountConfig.getNatConfig().setViaRewriteUse(pj_constants_.PJ_FALSE);
        /* rport 更新REGISTER请求的传输地址和Contact标头 */
        accountConfig.getNatConfig().setContactRewriteUse(pj_constants_.PJ_TRUE);

        /* 媒体配置 */
        /* 设置QOS流量类型 */
        accountConfig.getMediaConfig().getTransportConfig().setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);
        setVideoConfig(accountConfig);

        return accountConfig;
    }

    private void setVideoConfig(AccountConfig accountConfig) {
        /* 设置输出视频在拨出电话时默认是否被激活 */
        accountConfig.getVideoConfig().setAutoTransmitOutgoing(false);
        /* 输入视频是否应显示为默认屏幕 */
        accountConfig.getVideoConfig().setAutoShowIncoming(true);
        /* 设置默认视频捕获设备 */
        accountConfig.getVideoConfig().setDefaultCaptureDevice(CaptureDevice.FRONT_CAMERA);
        /* 设置默认渲染设备 */
        accountConfig.getVideoConfig().setDefaultRenderDevice(RenderDevice.OPENGL);
    }

    protected void removeCall(int callId) {
        SipCall call = activeCalls.get(callId);

        if (call != null) {
            XLog.tag(LOG_TAG).d("Removing call with ID: " + callId);
            activeCalls.remove(callId);
        }
    }

    public void create() throws Exception {
        this.create(Boolean.FALSE);
    }

    /**
     * 创建账户
     * @param makeDefault 是否设为默认账户
     * @throws Exception
     */
    public void create(boolean makeDefault) throws Exception {
        create(getAccountConfig(), makeDefault);
    }

    /**
     * 来电
     * @param callId
     * @return
     */
    protected SipCall incomingCall(int callId) {
        SipCall call = new SipCall(this, callId);
        activeCalls.put(callId, call);
        XLog.tag(LOG_TAG).d("Added incoming call with ID " + callId + " to " + data.getIdUri());
        return call;
    }

    /**
     * 外呼
     * @param dst
     * @return
     * @throws Exception
     */
    public SipCall outgoingCall(String dst) throws Exception {

        /* 检查通话数 */
        if (this.service.getTotalActiveCalls() > 0) {
            throw new Exception("The SipService is on the phone.");
        }

        SipCall call = new SipCall(this);
        CallOpParam callOpParam = new CallOpParam();

        if (!dst.startsWith("sip:")) {
            dst = "*".equals(data.getRealm()) ? "sip:" + dst : "sip:" + dst + "@" + data.getRealm();
        }
        call.makeCall(dst, callOpParam);

        this.activeCalls.put(call.getId(), call);
        XLog.tag(LOG_TAG).d("New outgoing call with ID: " + call.getId());
        return call;
    }

    /**
     * 注册/注销 回调
     * 只会通知初始注册和注销。一旦注册会话处于活动状态，后续刷新将不会导致此回调被调用
     * @param prm
     */
    @Override
    public void onRegStarted(OnRegStartedParam prm) {
        super.onRegStarted(prm);
        this.service.getEventEmitter().registrationStarted(this.data.getIdUri(), prm.getRenew());
    }

    /**
     * 注册状态变化回调
     * @param prm
     */
    @Override
    public void onRegState(OnRegStateParam prm) {
        super.onRegState(prm);
        this.service.getEventEmitter().registrationState(this.data.getIdUri(), prm.getStatus(), prm.getCode(), prm.getReason());
        /* 注册状态前台通知 */
        this.getService().stateNotification();
    }

    @Override
    public void onIncomingCall(OnIncomingCallParam prm) {
        SipCall call = incomingCall(prm.getCallId());

        /// TODO: Send 603 Decline if in DND mode

        if (this.service.getTotalActiveCalls() > 1) {
            /* 已有会话回复 486 busy here Send */
            try {
                call.answer(pjsip_status_code.PJSIP_SC_BUSY_HERE, "USER_BUSY");
                call.setDisused(Boolean.TRUE);
                XLog.tag(LOG_TAG).d("Sending busy to call ID: " + prm.getCallId());
            } catch(Exception ex) {
                XLog.tag(LOG_TAG).e("Error while getting missed call info", ex);
            }
            return;
        }

        try {
            /* 回复180 */
            call.answer(pjsip_status_code.PJSIP_SC_RINGING, null);
            XLog.tag(LOG_TAG).d("Sending 180 ringing");

            /* 播放振铃音 */
            MediaManager.getInstance().startRingTone(Boolean.TRUE);

            CallInfo callInfo = call.getInfo();
            /* 解析 remoteUri */
            SipUri.SipContactInfo contactInfo = SipUri.parseSipContact(callInfo.getRemoteUri());
            service.getEventEmitter().incomingCall(data.getIdUri(), prm.getCallId(), contactInfo.userName, contactInfo.displayName);
        } catch (Exception ex) {
            XLog.tag(LOG_TAG).e("Error while Answer Caller.", ex);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SipAccount that = (SipAccount) o;
        return data.equals(that.data);
    }

    @Override
    public int hashCode() {
        return data.hashCode();
    }
}
